
#include "stdafx.h"
#include "Sheet.h"
#include "assert.h"

#include "STP.h"

using namespace System::Drawing;
using namespace System::Diagnostics;

#define WS_EX_STATICEDGE        0x00020000L
#define WS_EX_WINDOWEDGE        0x00000100L
#define WS_EX_CLIENTEDGE        0x00000200L

// ============================================================================

Sheet::StateBeginningDrag::StateBeginningDrag (MouseEventArgs^ mouseDownEvent)
{
	this->mouseDownEvent = mouseDownEvent;
}

Sheet::StateDraggingBridge::StateDraggingBridge (Bridge^ bridge, float lastMouseX, float lastMouseY)
{
	this->bridge = bridge;
	this->lastMouseX = lastMouseX;
	this->lastMouseY = lastMouseY;
}

Sheet::StateDrawingNewWire::StateDrawingNewWire (Bridge^ startBridge, int startPortIndex)
{
	this->startBridge = startBridge;
	this->startPortIndex = startPortIndex;

	// we initialize the wire to zero length
	this->snappingBridge = startBridge;
	this->snappingPortIndex = startPortIndex;
}

Sheet::StateMovingWirePoint::StateMovingWirePoint (Wire^ wire, int pointIndex, float mouseX, float mouseY)
{
	this->wire = wire;
	this->pointIndex = pointIndex;

	// we start without snapping, even when we are in range of a port
	this->x = mouseX;
	this->y = mouseY;
}

Sheet::StateMovingInteriorPort::StateMovingInteriorPort (Bridge^ bridge, int portIndex)
{
	this->bridge = bridge;
	this->portIndex = portIndex;
}

Sheet::StateResizingBridge::StateResizingBridge (Bridge^ bridge, Corner corner, float mouseX, float mouseY)
{
	this->bridge = bridge;
	this->corner = corner;
	this->offsetX = mouseX - bridge->x;
	this->offsetY = mouseY - bridge->y;
	this->lastMouseX = mouseX;
	this->lastMouseY = mouseY;
}

// ============================================================================

Sheet::Sheet (TextBox^ logTextBox,
			  ComboBox^ logComboBoxPort,
			  ComboBox^ logComboBoxTree,
			  PropertyGrid^ bridgeOrPortPropertyGrid,
			  PropertyGrid^ treePropertyGrid)
	: UserControl ()
{
	InitializeComponent();

	this->logTextBox = logTextBox;
	this->logComboBoxPort = logComboBoxPort;
	this->logComboBoxTree = logComboBoxTree;
	this->logComboBoxPort->SelectedIndexChanged += gcnew EventHandler (this, &Sheet::OnFilterComboBoxChanged);
	this->logComboBoxTree->SelectedIndexChanged += gcnew EventHandler (this, &Sheet::OnFilterComboBoxChanged);
	this->bridgeOrPortPropertyGrid = bridgeOrPortPropertyGrid;
	this->treePropertyGrid = treePropertyGrid;

	SetStyle (ControlStyles::OptimizedDoubleBuffer, true);
	SetStyle (ControlStyles::UserPaint, true);
	SetStyle (ControlStyles::AllPaintingInWmPaint, true);
	SetStyle (ControlStyles::ResizeRedraw, true);
}

// ============================================================================

Sheet::~Sheet ()
{
	if (components)
	{
		delete components;
	}
}

// ============================================================================

System::Windows::Forms::CreateParams^ Sheet::CreateParams::get ()
{
	System::Windows::Forms::CreateParams^ cp = UserControl::CreateParams;

	cp->ExStyle |= WS_EX_CLIENTEDGE;

	return cp;
}

// ============================================================================

void Sheet::OnMouseDown (MouseEventArgs^ e)
{
	UserControl::OnMouseDown (e);

	Debug::Assert (state == nullptr);

	Bridge^ clickedBridge;

	// ------------------------------------------------------------------------
	// check for click on a bridge
	clickedBridge = GetBridgeAtCoords ((float) e->X, (float) e->Y);
	if (clickedBridge != nullptr)
	{
		// Clicked inside a bridge

		if (clickedBridge != selectedBridge)
		{
			// A new bridge, or a port in a new bridge, was selected. Let's bring the bridge's log on screen.
			logTextBox->Text = clickedBridge->GetBacklog (logComboBoxPort->SelectedIndex - 1, logComboBoxTree->SelectedIndex - 1);
			logTextBox->ScrollToCaret ();
		}

		int clickedPortIndex = GetInteriorPortAtCoords (clickedBridge, (float) e->X, (float) e->Y);

		if (clickedBridge != selectedBridge || clickedPortIndex != selectedPortIndex)
		{
			selectedBridge = clickedBridge;
			selectedPortIndex = clickedPortIndex;

			if (selectedPortIndex == -1)
			{
				// The bridge itself was selected, not one of its ports.
				bridgeOrPortPropertyGrid->SelectedObject = selectedBridge;
				treePropertyGrid->SelectedObject = selectedBridge->bridgeTrees [0];
			}
			else
			{
				// A port on the bridge was selected, not the bridge itself.
				bridgeOrPortPropertyGrid->SelectedObject = selectedBridge->ports [selectedPortIndex];
				treePropertyGrid->SelectedObject = selectedBridge->ports [selectedPortIndex]->trees [0];
			}

			Invalidate ();
		}
	}
	else
	{
		// Clicked outside any bridge.
		if (selectedBridge != nullptr)
		{
			// Deselect the currently-selected bridge/port.
			selectedBridge = nullptr;
			bridgeOrPortPropertyGrid->SelectedObject = nullptr;
			treePropertyGrid->SelectedObject = nullptr;
			logTextBox->Clear ();
			Invalidate ();
		}
	}

	state = gcnew StateBeginningDrag (e);
}

// ============================================================================

void Sheet::OnMouseMove_StateBeginningDrag (MouseEventArgs^ e)
{
	StateBeginningDrag^ s = (StateBeginningDrag^) this->state;

	if ((e->X < s->mouseDownEvent->X - StateBeginningDrag::Threshold)
		|| (e->X >= s->mouseDownEvent->X + StateBeginningDrag::Threshold)
		|| (e->Y < s->mouseDownEvent->Y - StateBeginningDrag::Threshold)
		|| (e->Y >= s->mouseDownEvent->Y + StateBeginningDrag::Threshold))
	{
		// dragged the required threshold
		Bridge^ clickedBridge;
		int clickedPortIndex;

		// Check for click on a non-wired exterior port
		if (GetExteriorPortAtCoords ((float) s->mouseDownEvent->X, (float) s->mouseDownEvent->Y, Port::SnappingRectangleSize, clickedBridge, clickedPortIndex))
		{
			if (IsPortWired (clickedBridge, clickedPortIndex) == false)
			{
				// creating a new wire
				state = gcnew StateDrawingNewWire (clickedBridge, clickedPortIndex);
				return;
			}
		}

		// Check for click on a wire point.
		
		for each (Wire^ wire in wires)
		{
			for (int pointIndex = 0; pointIndex < wire->points->Count; pointIndex++)
			{
				PointF p = wire->points [pointIndex]->GetCoords ();

				if (s->mouseDownEvent->X >= p.X - Port::SnappingRectangleSize / 2
					&& s->mouseDownEvent->X < p.X + Port::SnappingRectangleSize / 2
					&& s->mouseDownEvent->Y >= p.Y - Port::SnappingRectangleSize / 2
					&& s->mouseDownEvent->Y < p.Y + Port::SnappingRectangleSize / 2)
				{
					state = gcnew StateMovingWirePoint (wire, pointIndex, (float) e->X, (float) e->Y);
					return;
				}
			}
		}

		// Breaking a wire?
		// TODO:

		float mouseX = (float) s->mouseDownEvent->X;
		float mouseY = (float) s->mouseDownEvent->Y;
		float b = 20;

		// check for click on a bridge corner
		for each (Bridge^ bridge in Bridge::bridges)
		{
			if ((mouseX >= bridge->x) && (mouseX < bridge->x + b) && (mouseY >= bridge->y) && (mouseY < bridge->y + b))
			{
				state = gcnew StateResizingBridge (bridge, StateResizingBridge::Corner::UpperLeft, mouseX, mouseY);
				return;
			}

			if ((mouseX >= bridge->x + bridge->width - b) && (mouseX < bridge->x + bridge->width) && (mouseY >= bridge->y) && (mouseY < bridge->y + b))
			{
				state = gcnew StateResizingBridge (bridge, StateResizingBridge::Corner::UpperRight, mouseX, mouseY);
				return;
			}
		}

		if (selectedBridge != nullptr)
		{
			if (selectedPortIndex == -1)
			{
				state = gcnew StateDraggingBridge (selectedBridge, (float) s->mouseDownEvent->X, (float) s->mouseDownEvent->Y);
			}
			else
			{
				state = gcnew StateMovingInteriorPort (selectedBridge, selectedPortIndex);
			}
			
			return;
		}

	}
}

// ============================================================================

void Sheet::OnMouseMove_StateDraggingBridge (MouseEventArgs^ e)
{
	StateDraggingBridge^ state = (StateDraggingBridge^) this->state;

	selectedBridge->x += (float) e->X - state->lastMouseX;
	selectedBridge->y += (float) e->Y - state->lastMouseY;
	state->lastMouseX = (float) e->X;
	state->lastMouseY = (float) e->Y;

	Invalidate ();
}

void Sheet::OnMouseMove_StateDrawingNewWire (MouseEventArgs^ e)
{
	StateDrawingNewWire^ s = (StateDrawingNewWire^) state;

	Bridge^ snappingBridge;
	int snappingPortIndex;
	if (GetExteriorPortAtCoords ((float) e->X, (float) e->Y, Port::SnappingRectangleSize, snappingBridge, snappingPortIndex))
	{
		if (IsPortWired (snappingBridge, snappingPortIndex) == false)
		{
			// Snapping to another port.
			if ((s->snappingBridge != snappingBridge) || (s->snappingPortIndex != snappingPortIndex))
			{
				// Snapping to this port is beginning now.
				s->snappingBridge = snappingBridge;
				s->snappingPortIndex = snappingPortIndex;

				Invalidate ();
			}
		}
	}
	else
	{
		// No snapping.
		s->snappingBridge = nullptr;
		s->endX = (float) e->X;
		s->endY = (float) e->Y;
		Invalidate ();
	}
}

void Sheet::OnMouseMove_StateMovingWirePoint (MouseEventArgs^ e)
{
	StateMovingWirePoint^ s = (StateMovingWirePoint^) state;

	Bridge^ snappingBridge;
	int snappingPortIndex;
	if (GetExteriorPortAtCoords ((float) e->X, (float) e->Y, Port::SnappingRectangleSize, snappingBridge, snappingPortIndex))
	{
		// Mouse is on top of a port. Only allow if we are dragging the start or the end of the wire, and either:

		// Allow if we are dragging the start or the end of the wire.
		if ((s->pointIndex == 0) || (s->pointIndex == s->wire->points->Count - 1))
		{
			// and either:
			// - the port is free, or
			// - the port is already wired, and it is wired to the point we are currently dragging.
			if ((IsPortWired (snappingBridge, snappingPortIndex) == false)
				|| ((s->wire->points [s->pointIndex]->GetType () == Wire::PortPoint::typeid)
					&& (((Wire::PortPoint^) s->wire->points [s->pointIndex])->bridge == snappingBridge)
					&& (((Wire::PortPoint^) s->wire->points [s->pointIndex])->portIndex == snappingPortIndex)))
			{
				Wire::Point^ point = s->wire->points [s->pointIndex];

				// Did we just snap to this port?
				if ((s->snappingBridge != snappingBridge) || (s->snappingPortIndex != snappingPortIndex))
				{
					// Yes, we've just snapped.
					PointF portCoords = snappingBridge->GetCoordsForExteriorPort (snappingPortIndex);

					s->snappingBridge = snappingBridge;
					s->snappingPortIndex = snappingPortIndex;
					s->x = portCoords.X;
					s->y = portCoords.Y;
					Invalidate ();
				}
			}
		}
	}
	else
	{
		// Mouse is in air.

		// Did we just unsnap?
		if (s->wire->points [s->pointIndex]->GetType () != Wire::FreePoint::typeid)
		{
			// Yes, we've just unsnapped.
			s->snappingBridge = nullptr;
		}
			
		s->x = (float) e->X;
		s->y = (float) e->Y;

		Invalidate ();
	}
}

void Sheet::OnMouseMove_StateMovingInteriorPort (MouseEventArgs^ e)
{
	StateMovingInteriorPort^ s = (StateMovingInteriorPort^) state;

	s->bridge->SetCoordsForInteriorPort (s->portIndex, (float) e->X, (float) e->Y);

	Invalidate ();
}

void Sheet::OnMouseMove_StateResizingBridge (MouseEventArgs^ e)
{
	StateResizingBridge^ s = (StateResizingBridge^) state;
	float mouseX = (float) e->X;
	float mouseY = (float) e->Y;

	if (s->corner == StateResizingBridge::Corner::UpperLeft)
	{
		s->bridge->width -= mouseX - s->lastMouseX;
		s->bridge->x += mouseX - s->lastMouseX;
		s->bridge->height -= mouseY - s->lastMouseY;
		s->bridge->y += mouseY - s->lastMouseY;
		s->lastMouseX = mouseX;
		s->lastMouseY = mouseY;
	}
	else if (s->corner == StateResizingBridge::Corner::UpperRight)
	{
		s->bridge->width += mouseX - s->lastMouseX;
		s->bridge->height -= mouseY - s->lastMouseY;
		s->bridge->y += mouseY - s->lastMouseY;
		s->lastMouseX = mouseX;
		s->lastMouseY = mouseY;
	}
	else
		assert (false);

	Invalidate ();
}

// ============================================================================

void Sheet::OnMouseMove (MouseEventArgs^ e)
{
	UserControl::OnMouseMove (e);

	if (state == nullptr)
	{
	}
	else if (state->GetType () == StateBeginningDrag::typeid)
	{
		OnMouseMove_StateBeginningDrag (e);
	}
	else if (state->GetType () == StateDraggingBridge::typeid)
	{
		OnMouseMove_StateDraggingBridge (e);
	}
	else if (state->GetType () == StateDrawingNewWire::typeid)
	{
		OnMouseMove_StateDrawingNewWire (e);
	}
	else if (state->GetType () == StateMovingWirePoint::typeid)
	{
		OnMouseMove_StateMovingWirePoint (e);
	}
	else if (state->GetType () == StateMovingInteriorPort::typeid)
	{
		OnMouseMove_StateMovingInteriorPort (e);
	}
	else if (state->GetType () == StateResizingBridge::typeid)
	{
		OnMouseMove_StateResizingBridge (e);
	}
	else
		Debug::Assert (false);
}

// ============================================================================

void Sheet::OnMouseUp_StateDrawingNewWire (MouseEventArgs^ e)
{
	StateDrawingNewWire^ s = (StateDrawingNewWire^) state;

	if ((s->startBridge == s->snappingBridge) && (s->startPortIndex == s->snappingPortIndex))
	{
		// Zero-length wire. We don't create it.
	}
	else if (s->snappingBridge != nullptr)
	{
		// The wire ends at another port (not in the air).
		Wire^ newWire = gcnew Wire (s->startBridge, s->startPortIndex, s->snappingBridge, s->snappingPortIndex);
		wires->Add (newWire);

		Debug::Assert (s->startBridge->ports [s->startPortIndex]->portEnabled == false);
		s->startBridge->OnPortEnabled (s->startPortIndex, 100, true);

		Debug::Assert (s->snappingBridge->ports [s->snappingPortIndex]->portEnabled == false);			
		s->snappingBridge->OnPortEnabled (s->snappingPortIndex, 100, true);
	}
	else
	{
		// The wire ends in the air. Don't allow
/*		Wire^ wire = gcnew Wire (s->_startBridgeIndex, s->startPortIndex, -1, -1);
		wire->_points->Add (e->Location);
		wires->Add (wire);
*/
	}

	Invalidate ();
	state = nullptr;
}

// ============================================================================

void Sheet::OnMouseUp_StateMovingWirePoint (MouseEventArgs^ e)
{
	StateMovingWirePoint^ s = (StateMovingWirePoint^) this->state;

	if (s->snappingBridge != nullptr)
	{
		// The point was dragged to a port before this mouse event, so it must be a wire end.
		Debug::Assert ((s->pointIndex == 0) || (s->pointIndex == s->wire->points->Count - 1));

		// Was the dragged point snapped to a port before this mouse event? (or we just snapped it?)
		if (s->wire->points [s->pointIndex]->GetType () == Wire::PortPoint::typeid)
		{
			// Yes. The dragged point was snapped before too.
			Wire::PortPoint^ oldPoint = (Wire::PortPoint^) s->wire->points [s->pointIndex];
			
			// Was the dragged point snapped to the same port?
			if ((oldPoint->bridge == s->snappingBridge) && (oldPoint->portIndex == s->snappingPortIndex))
			{
				// Yes. The dragged point was snapped before to the same port, so nothing changed.
			}
			else
			{
				// No. The dragged point was snapped before to some other port.
				
				Wire::PortPoint^ newPoint = gcnew Wire::PortPoint (s->snappingBridge, s->snappingPortIndex);

				// Was the other end snapped too?
				Wire::Point^ otherEnd;
				if (s->pointIndex == 0)
					otherEnd = s->wire->points [s->wire->points->Count - 1];
				else
					otherEnd = s->wire->points [0];

				if (otherEnd->GetType () == Wire::PortPoint::typeid)
				{
					// Yes, the other end was snapped to a port too.

					// The ports at both wire ends must have been enabled then.
					Wire::PortPoint^ leftEnd = (Wire::PortPoint^) s->wire->points [0];
					Wire::PortPoint^ rightEnd = (Wire::PortPoint^) s->wire->points [s->wire->points->Count - 1];
					Debug::Assert (leftEnd->bridge->ports [leftEnd->portIndex]->portEnabled);
					Debug::Assert (rightEnd->bridge->ports [rightEnd->portIndex]->portEnabled);

					// Let's disable both ports
					leftEnd->bridge->OnPortDisabled (leftEnd->portIndex);
					rightEnd->bridge->OnPortDisabled (rightEnd->portIndex);

					// and enable the two new ports
					newPoint->bridge->OnPortEnabled (newPoint->portIndex, 100, true);
					static_cast<Wire::PortPoint^> (otherEnd)->bridge->OnPortEnabled (static_cast<Wire::PortPoint^> (otherEnd)->portIndex, 100, true);
				}
				else
				{
					// No, the other end wasn't snapped to a port, it was in the air.

					// The port at the dragged end must have been disabled then.
					Debug::Assert (oldPoint->bridge->ports [oldPoint->portIndex]->portEnabled == false);
				}

				// Make the dragged end point to the new port,
				s->wire->points [s->pointIndex] = newPoint;
			}	
		}
		else
		{
			// No. The dragged point was not snapped before. We've just snapped it to this port.

			// Since we just snapped it to this port, this port must have been disabled.
			Debug::Assert (s->snappingBridge->ports [s->snappingPortIndex]->portEnabled == false);

			// Is the other end snapped?
			Wire::Point^ otherEnd;
			if (s->pointIndex == 0)
				otherEnd = s->wire->points [s->wire->points->Count - 1];
			else
				otherEnd = s->wire->points [0];

			if (otherEnd->GetType () == Wire::PortPoint::typeid)
			{
				// Yes, the other end is snapped to a port.

				// That port must not have been enabled, because this end was in the air.
				// Let's enable both ports now.
				s->snappingBridge->OnPortEnabled (s->snappingPortIndex, 100, true);

				Wire::PortPoint^ otherEndPortPoint = (Wire::PortPoint^) otherEnd;
				otherEndPortPoint->bridge->OnPortEnabled (otherEndPortPoint->portIndex, 100, true);
			}
			else
			{
				// No, the other end isn't snapped to a port, it is in the air.

				// The port at the dragged end must have been disabled then.
				Debug::Assert (s->snappingBridge->ports [s->snappingPortIndex]->portEnabled == false);
			}

			// Let's transform the dragged point into a new PortPoint.
			Wire::PortPoint^ newPoint = gcnew Wire::PortPoint (s->snappingBridge, s->snappingPortIndex);
			s->wire->points [s->pointIndex] = newPoint;
		}
	}
	else
	{
		// The dragged point is now in the air.

		// Was it snapped to a port before?
		if (s->wire->points [s->pointIndex]->GetType () == Wire::PortPoint::typeid)
		{
			// Yes, it was, so it must be a wire end.
			Debug::Assert ((s->pointIndex == 0) || (s->pointIndex == s->wire->points->Count - 1));
			
			// Was the other end of this wire snapped to a port too?
			Wire::Point^ otherEnd;
			if (s->pointIndex == 0)
				otherEnd = s->wire->points [s->wire->points->Count - 1];
			else
				otherEnd = s->wire->points [0];

			if (otherEnd->GetType () == Wire::PortPoint::typeid)
			{
				// Yes, the other end was snapped to a port too.

				// The ports at both wire ends must have been enabled then.
				Wire::PortPoint^ leftEnd = (Wire::PortPoint^) s->wire->points [0];
				Wire::PortPoint^ rightEnd = (Wire::PortPoint^) s->wire->points [s->wire->points->Count - 1];
				Debug::Assert (leftEnd->bridge->ports [leftEnd->portIndex]->portEnabled);
				Debug::Assert (rightEnd->bridge->ports [rightEnd->portIndex]->portEnabled);

				// And now we disabled them, as this end has just been dragged to the air.
				leftEnd->bridge->OnPortDisabled (leftEnd->portIndex);
				rightEnd->bridge->OnPortDisabled (rightEnd->portIndex);
			}
			else
			{
				// No, the other end wasn't snapped to a port, it was in the air.

				// The port at this end must have been disabled then.
				Wire::PortPoint^ draggedEnd = (Wire::PortPoint^) s->wire->points [s->pointIndex];
				Debug::Assert (draggedEnd->bridge->ports [draggedEnd->portIndex]->portEnabled == false);
			}
		}

		// And now make the dragged point a FreePoint.
		s->wire->points [s->pointIndex] = gcnew Wire::FreePoint (s->x, s->y);
	}

	Invalidate ();
	state = nullptr;
}

// ============================================================================

void Sheet::OnMouseUp (MouseEventArgs^ e)
{
	UserControl::OnMouseUp (e);

	if (state == nullptr)
	{
	}
	else if (state->GetType () == StateBeginningDrag::typeid)
	{
		state = nullptr;
	}
	else if (state->GetType () == StateDraggingBridge::typeid)
	{
		state = nullptr;
	}
	else if (state->GetType () == StateDrawingNewWire::typeid)
	{
		OnMouseUp_StateDrawingNewWire (e);
	}
	else if (state->GetType () == StateMovingWirePoint::typeid)
	{
		OnMouseUp_StateMovingWirePoint (e);
	}
	else if (state->GetType () == StateMovingInteriorPort::typeid)
	{
		state = nullptr;
	}
	else if (state->GetType () == StateResizingBridge::typeid)
	{
		state = nullptr;
	}
	else
		throw gcnew Exception ();
}

// ============================================================================

struct INFO
{
	const char* str;
	PORT_ROLE role;
	bool learning;
	bool forwarding;
	bool operEdge;
};

static const INFO infos [] = 
{
	{"Disabled",						PORT_ROLE_DISABLED, false, false, false},

	{"Designated discarding",			PORT_ROLE_DESIGNATED, false, false, false},
	{"Designated learning",				PORT_ROLE_DESIGNATED, true, false, false},
	{"Designated forwarding",			PORT_ROLE_DESIGNATED, true, true, false},
	{"Designated forwarding operEdge",	PORT_ROLE_DESIGNATED, true, true, true},

	{"Root/Master discarding",			PORT_ROLE_ROOT, false, false, false},
	{"Root/Master learning",			PORT_ROLE_ROOT, true, false, false},
	{"Root/Master forwarding",			PORT_ROLE_ROOT, true, true, false},

	{"Alternate discarding",			PORT_ROLE_ALTERNATE, false, false, false},
	{"Alternate learning",				PORT_ROLE_ALTERNATE, true, false, false},

	{"Backup discarding",				PORT_ROLE_BACKUP, false, false, false},
//	{"Backup learning",					PORT_ROLE_ALTERNATE, true, false, false},
};

void Sheet::PaintLegend (Graphics^ g)
{
	System::Drawing::Font^ font = SystemFonts::MessageBoxFont;

	SizeF textSize = g->MeasureString ("Designated forwarding operEdge", font);

	float bitmapHeight = Port::ExteriorDrawingWidth;
	float bitmapWidth = Port::ExteriorDrawingHeight;
	float textX = (float) ClientSize.Width - (5 + textSize.Width + 5 + bitmapWidth + 5);
	float bitmapX = (float) ClientSize.Width - (5 + bitmapWidth + 5);
	float rowHeight = (textSize.Height > bitmapHeight ? textSize.Height : bitmapHeight);
	float bitmapOffsetY = (rowHeight - bitmapHeight) / 2;
	int rowCount = sizeof (infos) / sizeof (INFO);
	float y = this->ClientSize.Height - rowCount * rowHeight;

	for (int i = 0; i < rowCount; i++)
	{
		const INFO* info = &infos [i];

		g->DrawString (gcnew String (info->str), font, SystemBrushes::WindowText, textX, y);
		g->DrawImage (Port::GetBitmap (info->role, info->learning, info->forwarding, info->operEdge, RotateFlipType::Rotate270FlipNone), bitmapX, y + bitmapOffsetY);
		g->DrawLine (SystemPens::WindowText, textX, y, (float) ClientSize.Width, y);
		y += rowHeight;
	}
}

// ============================================================================

void Sheet::OnPaint (PaintEventArgs^ e)
{
	UserControl::OnPaint (e);

	PaintLegend (e->Graphics);

	for each (Bridge^ bridge in Bridge::bridges)
		bridge->Paint (e->Graphics, bridge == selectedBridge, selectedPortIndex);

	// Turn on antialiasing before drawing the.
	e->Graphics->SmoothingMode = System::Drawing::Drawing2D::SmoothingMode::AntiAlias;

	// Paint the wires
	for each (Wire^ wire in wires)
		wire->Paint (e->Graphics);

	if (state == nullptr)
	{
	}
	else if (state->GetType () == StateDrawingNewWire::typeid)
	{
		// Paint the wire being created.
		StateDrawingNewWire^ s = (StateDrawingNewWire^) state;

		PointF start = s->startBridge->GetCoordsForExteriorPort (s->startPortIndex);
		PointF end;
		if (s->snappingBridge != nullptr)
			end = s->snappingBridge->GetCoordsForExteriorPort (s->snappingPortIndex);
		else
		{
			end.X = s->endX;
			end.Y = s->endY;
		}

		e->Graphics->DrawLine (Wire::tempWirePen, start, end);
	}
	else if (state->GetType () == StateMovingWirePoint::typeid)
	{
		// Paint the sections of the wire that are to the left and to the right of the point that is being moved.
		StateMovingWirePoint^ s = (StateMovingWirePoint^) state;

		if (s->pointIndex > 0)
		{
			// This is not the first point, so we can paint the section on the left.
			PointF start = s->wire->points [s->pointIndex - 1]->GetCoords ();

			e->Graphics->DrawLine (Wire::tempWirePen, start.X, start.Y, s->x, s->y);
		}

		if (s->pointIndex < s->wire->points->Count - 1)
		{
			// This is not the last point, so we can paint the section on the right.
			PointF end = s->wire->points [s->pointIndex + 1]->GetCoords ();

			e->Graphics->DrawLine (Wire::tempWirePen, s->x, s->y, end.X, end.Y);
		}
	}
}

// ============================================================================

void Sheet::AddNewBridge (int portCount)
{
	Bridge^ newBridge = gcnew Bridge (nullptr, portCount, 1,
		gcnew DebugStrOutCallback (this, &Sheet::OnDebugStrOut),
		gcnew BpduTransmitCallback (this, &Sheet::OnBpduTransmitCallback));

	Invalidate ();
}

// ============================================================================

Bridge^ Sheet::GetBridgeAtCoords (float x, float y)
{
	for (int i = 0; i < Bridge::bridges->Count; i++)
	{
		Bridge^ bridge = Bridge::bridges [i];

		if ((x >= bridge->x) && (x < bridge->x + bridge->width)
			&& (y >= bridge->y) && (y < bridge->y + bridge->height))
		{
			return bridge;
		}
	}

	return nullptr;
}

// ============================================================================

int Sheet::GetInteriorPortAtCoords (Bridge^ bridge, float x, float y)
{
	for (int portIndex = 0; portIndex < bridge->ports->Length; portIndex++)
	{
		Port^ port = bridge->ports [portIndex];

		RectangleF r = bridge->GetRectForInteriorPort (portIndex);

		if ((x >= r.X) && (y >= r.Y) && (x < r.X + r.Width) && (y < r.Y + r.Height))
			return portIndex;
	}

	return -1;
}

// ============================================================================

bool Sheet::GetExteriorPortAtCoords (float x, float y, float rectangleSize, Bridge^% bridgeOut, int% portIndexOut)
{
	for (int bridgeIndex = 0; bridgeIndex < Bridge::bridges->Count; bridgeIndex++)
	{
		Bridge^ bridge = Bridge::bridges [bridgeIndex];

		for (int portIndex = 0; portIndex < bridge->ports->Length; portIndex++)
		{
			Port^ port = bridge->ports [portIndex];

			PointF p = bridge->GetCoordsForExteriorPort (portIndex);

			if ((x >= p.X - rectangleSize / 2) && (x < p.X + rectangleSize / 2)
				&& (y >= p.Y - rectangleSize / 2) && (y < p.Y + rectangleSize / 2))
			{
				bridgeOut = bridge;
				portIndexOut = portIndex;
				return true;
			}
		}
	}

	return false;
}

// ============================================================================

bool Sheet::IsPortWired (Bridge^ bridge, int portIndex)
{
	for each (Wire^ wire in wires)
	{
		Wire::Point^ p = wire->points [0];
		if ((p->GetType () == Wire::PortPoint::typeid)
			&& (((Wire::PortPoint^) p)->bridge == bridge)
			&& (((Wire::PortPoint^) p)->portIndex == portIndex))
		{
			return true;
		}

		p = wire->points [wire->points->Count - 1];
		if ((p->GetType () == Wire::PortPoint::typeid)
			&& (((Wire::PortPoint^) p)->bridge == bridge)
			&& (((Wire::PortPoint^) p)->portIndex == portIndex))
		{
			return true;
		}
	}

	return false;
}

// ============================================================================
/*
PointF Sheet::GetCoordsForPoint (Wire::Point^ point)
{
	if (point->GetType () == Wire::FreePoint::typeid)
		return PointF (((Wire::FreePoint^) point)->x, ((Wire::FreePoint^) point)->y);

	if (point->GetType () == Wire::PortPoint::typeid)
	{
		Wire::Port
		return ((Wire::PortPoint^) point)->bridge->GetCoordsForExteriorPort  (((Wire::PortPoint^) point)->portIndex);

	Debug::Assert (false);
	return Point (0, 0);
}
*/
// ============================================================================

void Sheet::contextMenuStripBridge_Opening (Object^ sender, CancelEventArgs^ e)
{
	Debug::Assert (selectedBridge != nullptr);
	Debug::Assert (selectedPortIndex == -1);

	if (selectedBridge->Started)
	{
		powerUpToolStripMenuItem->Visible = false;
		powerDownToolStripMenuItem->Visible = true;
	}
	else
	{
		powerUpToolStripMenuItem->Visible = true;
		powerDownToolStripMenuItem->Visible = false;
	}
}

// ============================================================================

void Sheet::OnMouseClick (MouseEventArgs^ e)
{
	if ((e->Button & System::Windows::Forms::MouseButtons::Right) == System::Windows::Forms::MouseButtons::Right)
	{
		if (selectedBridge != nullptr && selectedPortIndex == -1)
			contextMenuStripBridge->Show (this, e->X, e->Y);
	}
}

// ============================================================================

void Sheet::powerUpToolStripMenuItem_Click (Object^ sender, EventArgs^ e)
{
	Debug::Assert (selectedBridge != nullptr);
	Debug::Assert (selectedPortIndex == -1);

	selectedBridge->Start ();
	Invalidate ();
}

// ============================================================================

void Sheet::powerDownToolStripMenuItem_Click (Object^ sender, EventArgs^ e)
{
	Debug::Assert (selectedBridge != nullptr);
	Debug::Assert (selectedPortIndex == -1);

	selectedBridge->Stop ();
	Invalidate ();
}

// ============================================================================

void Sheet::OnDebugStrOut (Bridge^ bridge, int portIndex, int treeIndex, String^ str)
{
	if (bridge == selectedBridge)
	{
		if (((portIndex == -1) || (logComboBoxPort->SelectedIndex == 0) || (portIndex == logComboBoxPort->SelectedIndex - 1))
			&& ((treeIndex == -1) || (logComboBoxTree->SelectedIndex == 0) || (treeIndex == logComboBoxTree->SelectedIndex - 1)))
		{
			logTextBox->AppendText (str);
		}
	}
}

// ============================================================================

void Sheet::OnFilterComboBoxChanged (Object^ sender, EventArgs^ e)
{
	if (selectedBridge != nullptr)
	{
		logTextBox->Text = selectedBridge->GetBacklog (logComboBoxPort->SelectedIndex - 1, logComboBoxTree->SelectedIndex - 1);
		logTextBox->ScrollToCaret ();
	}
}

// ============================================================================

void Sheet::timer1_Tick (Object^ sender, EventArgs^ e)
{
	for each (Bridge^ bridge in Bridge::bridges)
	{
		try
		{
			bridge->OnTick ();
		}
		catch (Exception^ e)
		{
			timer1->Enabled = false;

			MessageBox::Show (e->Message);
		}
	}

	Invalidate ();
}

// ============================================================================

void Sheet::OnBpduTransmitCallback (Bridge^ bridge, int portIndex, const unsigned char* bpdu, unsigned int bpduSize)
{
	for each (Wire^ wire in wires)
	{
		if ((wire->points [0]->GetType () == Wire::PortPoint::typeid)
			&& (wire->points [wire->points->Count - 1]->GetType () == Wire::PortPoint::typeid))
		{
			Wire::PortPoint^ leftEnd = (Wire::PortPoint^) wire->points [0];
			Wire::PortPoint^ rightEnd = (Wire::PortPoint^) wire->points [wire->points->Count - 1];

			if ((leftEnd->bridge == bridge) && (leftEnd->portIndex == portIndex))
			{
				// transmit this bpdu from the left end to the the right end
				rightEnd->bridge->OnBpduReceived (bpdu, bpduSize, rightEnd->portIndex);
				break;
			}
			else if ((rightEnd->bridge == bridge) && (rightEnd->portIndex == portIndex))
			{
				// transmit this bpdu from the right end to the the left end
				leftEnd->bridge->OnBpduReceived (bpdu, bpduSize, leftEnd->portIndex);
				break;
			}
		}
	}
}

// ============================================================================

void Sheet::StartAll ()
{
	for each (Bridge^ bridge in Bridge::bridges)
	{
		if (bridge->Started == false)
			bridge->Start ();
	}

	Invalidate ();
}

void Sheet::StopAll ()
{
	for each (Bridge^ bridge in Bridge::bridges)
	{
		if (bridge->Started)
			bridge->Stop ();
	}

	Invalidate ();
}

void Sheet::PauseAll ()
{
	if (timer1->Enabled)
		timer1->Enabled = false;
}

void Sheet::ResumeAll ()
{
	if (timer1->Enabled == false)
		timer1->Enabled = true;
}

// ============================================================================

void Sheet::SaveToFile (String^ filename)
{
	FileStream^ fs = gcnew FileStream (filename, FileMode::Create);
	BinaryWriter^ bw = gcnew BinaryWriter (fs);

	bw->Write (gcnew String (L"EDGB"));

	Bridge::SerializeBridges (bw);

	bw->Write (gcnew String (L"Wires"));

	bw->Write (wires->Count);
	for (int i = 0; i < wires->Count; i++)
		wires [i]->Serialize (bw);

	bw->Flush ();

	fs->Close ();
}

// ============================================================================

void Sheet::LoadFromFile (String^ filename)
{
	FileStream^ fs = gcnew FileStream (filename, FileMode::Open);
	BinaryReader^ br = gcnew BinaryReader (fs);

	String^ signature = br->ReadString ();
	assert (signature == L"EDGB");

	Bridge::DeserializeBridges (br,
		gcnew DebugStrOutCallback (this, &Sheet::OnDebugStrOut),
		gcnew BpduTransmitCallback (this, &Sheet::OnBpduTransmitCallback));

	signature = br->ReadString ();
	assert (signature == L"Wires");

	wires->Clear ();

	int wireCount = br->ReadInt32 ();
	for (int i = 0; i < wireCount; i++)
	{
		Wire^ wire = gcnew Wire (br);
		wires->Add (wire);
	}

	fs->Close ();

	Invalidate ();
}

